#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Converters.h>
#include <Kharlia/Engine/Init.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Core/Converters.h>

#include <CEGUIUDim.h>

#include <Kharlia/Engine/Debug.h>
#include <boost/type_traits/is_pod.hpp>

namespace kharlia { namespace engine {
    // Types in other namespaces must be typedefed for converter macro compatibility

    typedef ui::UDim CEGUI_UDim;

    // UI::UDim - Python Tuple
    struct CEGUI_UDim_To {
        static PyObject* convert(const ui::UDim& v) {
            tuple t = make_tuple(v.d_scale, v.d_offset);
            return incref(t.ptr());
        }
    };
    struct CEGUI_UDim_From {
        static void* convertible(PyObject* obj) {
            if (PyTuple_Check(obj) && PyTuple_Size(obj) == 2) {
                if (PyNumber_Check(PyTuple_GET_ITEM(obj, 0)) && PyNumber_Check(PyTuple_GET_ITEM(obj, 1))) {
                    return obj;
                }
            }
            return NULL;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            PyObject *f1o, *f2o;
            float f1, f2;
            if (PyFloat_Check(PyTuple_GET_ITEM(obj, 0))) {
                f1 = float(PyFloat_AS_DOUBLE(PyTuple_GET_ITEM(obj, 0)));
            }
            else {
                f1o = PyNumber_Float(PyTuple_GET_ITEM(obj, 0));
                f1 = float(PyFloat_AS_DOUBLE(f1o));
                Py_DECREF(f1o);
            }
            if (PyFloat_Check(PyTuple_GET_ITEM(obj, 1))) {
                f2 = float(PyFloat_AS_DOUBLE(PyTuple_GET_ITEM(obj, 1)));
            }
            else {
                f2o = PyNumber_Float(PyTuple_GET_ITEM(obj, 1));
                f2 = float(PyFloat_AS_DOUBLE(f1o));
                Py_DECREF(f2o);
            }
            KH_GET_STORAGE(CEGUI_UDim);
            new(storage) CEGUI_UDim(f1, f2);
            KH_SET_STORAGE;

        }
    };

    // FVector2I from sequence

    struct Vector2I_From {
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                return obj;
            }
            return NULL;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));

            int n1 = int(PyInt_AS_LONG(PyInt_CheckExact(o1.get()) ? o1.get() : handle<>(PyNumber_Int(o1.get())).get()));
            int n2 = int(PyInt_AS_LONG(PyInt_CheckExact(o2.get()) ? o2.get() : handle<>(PyNumber_Int(o2.get())).get()));

            KH_GET_STORAGE(FVector2I);
            new(storage) FVector2I(n1, n2);
            KH_SET_STORAGE;
        }
    };
    
    // FVector2F from sequence

    struct Vector2F_From {
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                return obj;
            }
            return NULL;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));

            float n1 = float(PyFloat_AS_DOUBLE(PyFloat_CheckExact(o1.get()) ? o1.get() : handle<>(PyNumber_Float(o1.get())).get()));
            float n2 = float(PyFloat_AS_DOUBLE(PyFloat_CheckExact(o2.get()) ? o2.get() : handle<>(PyNumber_Float(o2.get())).get()));

            KH_GET_STORAGE(FVector2F);
            new(storage) FVector2F(n1, n2);
            KH_SET_STORAGE;
        }
    };

    template <class T>
    struct TVector3_From {
        C_ASSERT(boost::is_pod<T>::value);
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 3) {
                return obj;
            }
            return NULL;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));
            handle<> o3(PySequence_GetItem(obj, 2));

            T n1 = extract<T>(o1.get());
            T n2 = extract<T>(o2.get());
            T n3 = extract<T>(o3.get());

            KH_GET_STORAGE(TVector3<T>);
            new(storage) TVector3<T>(n1, n2, n3);
            KH_SET_STORAGE;
        }
    };

    template <class T>
    struct TSize_From {
        C_ASSERT(boost::is_pod<T>::value);
        static void* convertible(PyObject* obj) {
            if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
                return obj;
            }
            return NULL;
        }
        static void construct(PyObject* obj, converter::rvalue_from_python_stage1_data* data) {
            handle<> o1(PySequence_GetItem(obj, 0));
            handle<> o2(PySequence_GetItem(obj, 1));

            T n1 = extract<T>(o1.get());
            T n2 = extract<T>(o2.get());

            KH_GET_STORAGE(TSize<T>);
            new(storage) TSize<T>(n1, n2);
            KH_SET_STORAGE;
        }
    };

    // Helper functions for registering converters

    template <class Type, class ToStruct>
    static void RegTo() {
        to_python_converter<Type, ToStruct>();
    }
    template <class Type, class FromStruct>
    static void RegFrom() {
        converter::registry::push_back(
            &FromStruct::convertible,
            &FromStruct::construct,
            type_id<Type>()
        );
    }
    template <class Type, class ToStruct, class FromStruct>
    static void RegCon() {
        RegTo<Type, ToStruct>();
        RegFrom<Type, FromStruct>();
    }
    // Called from the BOOST_PYTHON_MODULE
    void _InitConverters() {
        //to_python_converter<CEGUI_UDim, CEGUI_UDim_To>();
        //converter::registry::push_back(&CEGUI_UDim_From::convertible,
        //                                   &CEGUI_UDim_From::construct,
        //                                    type_id<CEGUI_UDim>());

        RegCon<CEGUI_UDim, CEGUI_UDim_To, CEGUI_UDim_From>();
        RegFrom<FVector2I, Vector2I_From>();
        RegFrom<FVector2F, Vector2F_From>();
        //RegFrom<FVector3I, Vector3I_From>();
        //RegFrom<FVector3F, Vector3F_From>();
        RegFrom<FVector3I, TVector3_From<FVector3I::DataType>>();
        RegFrom<FVector3F, TVector3_From<FVector3F::DataType>>();
        RegFrom<FSizeI, TSize_From<FSizeI::DataType>>();
        RegFrom<FSizeF, TSize_From<FSizeF::DataType>>();
    }

}}